<?php
class Pageley_Gateway_Database_StandardQuerySqlDecorator implements Pageley_Gateway_Database_QuerySqlDecorator {
	private $decoratedQuery;
	private $decoratedCondition;
    private $database;
    private $table;
	private $sql;
	private $aliases;
	private $eagerReferences;

	public function __construct($decoratedQuery, $database) {
		$this->decoratedQuery = $decoratedQuery;
		$this->database = $database;
		$condition = $this->decoratedQuery->getCondition();
		if($condition != null) {
        	$this->setCondition($condition);
		}
        $this->sql = new Pageley_Util_ArrayMap();
        $this->table = $database->getTable($this->getType());
        $this->eagerReferences = new Pageley_Util_ArrayMap();
        foreach($this->getType()->getAttributes() as $propertyName => $attribute) {
            if($attribute->isReference() && $attribute->isEager()) {
                $this->eagerReferences->put($propertyName, $attribute);
            }
        }
	}

	public function matches($entity) {
		return $this->decoratedQuery->matches($entity);
	}

	public function getNextIndex($propertyName) {
		return $this->decoratedQuery->getNextIndex($propertyName);
	}

	public function getType() {
		return $this->decoratedQuery->getType();
	}

	public function getCondition() {
		return $this->decoratedQuery->getCondition();
	}

	public function setCondition($condition) {
	    $this->decoratedQuery->setCondition($condition);
		$this->decoratedCondition = Pageley_Gateway_Database_Condition_AbstractSqlDecorator::decorateCondition(
                $condition, $this);
	}

	public function toSql($queryType = null) {
        if($queryType === null) {
            $queryType = Pageley_Gateway_Database_QueryType::$DEFAULT;
        }
		if($this->sql->get($queryType->getName()) === null) {
			$this->sql->put($queryType->getName(), $this->createSql($queryType));
		}
		return $this->sql->get($queryType->getName());
	}

	private function createSql($queryType) {
		$this->aliases = new Pageley_Util_ArrayMap();
        $alias = $this->getAlias($this->getType());
		return ($queryType == Pageley_Gateway_Database_QueryType::$COUNT ?
                $this->getCountClause($alias) :
                $this->getSelectClause($alias)) .
			$this->getFromClause($alias) .
			$this->getWhereClause($alias) .
            $this->getOrderByClause($alias) .
            $this->getRangeClause($alias);
	}
    
    private function getSelectClause($alias) {
        return "SELECT " . $this->getColumnNames($alias);
    }
    
    private function getCountClause($alias) {
        return "SELECT COUNT(*) AS \"count\"";
    }
    
    private function getColumnNames($alias) {
        $columnNameGenerator = $this->getColumnNameGenerator();
        $columnNames = $columnNameGenerator->getColumnNames();
        foreach($this->eagerReferences as $propertyName => $attribute) {
            $type = $attribute->getType();
            $table = $this->getTable($type);
            $a = Pageley_Util_String::addUnderscores($propertyName);
            $columnNameGenerator = $table->getColumnNameGenerator($a . "_");
            $columnNames .= ", " . $columnNameGenerator->getColumnNames();
        }
        return $columnNames;
    }
    
    private function getFromClause($alias) {
        $fromClause = " FROM " . $this->table->getFullTableName() . " AS " . $alias;
        $generator = $this->getColumnNameGenerator();
        foreach($this->eagerReferences as $propertyName => $attribute) {
            $type = $attribute->getType();
            $table = $this->getTable($type);
            $joinedGenerator = $table->getColumnNameGenerator();
            $joinedAlias = $this->getAlias($type);
            $fromClause .= " INNER JOIN " . $table->getFullTableName() . " AS " . $joinedAlias 
                . " ON " . $alias . ".\"" . $generator->getColumnName($propertyName, $attribute) . "\" = "
                . $joinedAlias . ".\"" . $joinedGenerator->getIdColumnName() . "\"";
        }
        if($this->decoratedCondition !== null) {
            $fromClause .= $this->decoratedCondition->getFromClause();
        }
        return $fromClause;
    }
    
    private function getWhereClause($alias) {
        $condition = $this->decoratedCondition;
        return ($condition !== null ?
			" WHERE " . $condition->toSql() : "");
    }
    
    private function getOrderByClause($alias) {
        $clause = null;
        $order = $this->getOrder();
        if($order !== null) {
            foreach($order as $propertyName => $direction) {
                if($clause === null) {
                    $clause = " ORDER BY ";
                } else {
                    $clause .= ", ";
                }
                $clause .= $alias . ".\"" . $this->getColumnName($propertyName) . "\" " . 
                    ($direction->isAscending() ? "ASC" : "DESC");
            }
        } else {
            $clause = "";
        }
        return $clause;
    }
    
    protected function getRangeClause($alias) {
        $range = $this->getRange();
        return $range !== null ? " OFFSET " . ($range->getStart() - 1) . " ROWS "
                . "FETCH FIRST " . ($range->getEnd() - $range->getStart() + 1) . " ROWS ONLY" : "";
    }

	public function getAlias($type) {
		$name = $type->getName();
		$alias = $this->aliases->get($name);
		if($alias === null) {
			$char = Pageley_Util_String::toLowercase(Pageley_Util_String::substring($name, 0, 1));
			$i = 1;
			do {
				$alias = $char . $i;
				$i++;
			} while($this->aliases->containsValue($alias));
			$this->aliases->put($name, $alias);
		}
		return $alias;
	}

	public function getValues() {
		$values = null;
        if($this->decoratedCondition !== null) {
            $values = $this->decoratedCondition->getValues();
        } else {
            $values = new Pageley_Util_ArrayMap();
        }
        return $values;
	}
    
    public function getRange() {
        return $this->decoratedQuery->getRange();
    }
    
    public function getOrder() {
        return $this->decoratedQuery->getOrder();
    }
    
    public function getColumnName($propertyName) {
		$columnNameGenerator = $this->getColumnNameGenerator();
        return $columnNameGenerator->getColumnName($propertyName);
    }
    
    private function getColumnNameGenerator() {
        return $this->table->getColumnNameGenerator();
    }
    
    public function getTable($type) {
        return $this->database->getTable($type);
    }
}
